//
// serialio.c
//
// Linux, OS X and Windows cross platform serial port access code
//
// The code is refactored out of the original implementation in lpc21isp.c by
// Omnima Limited (c) 2008-2011 All Rights Reserved
//
// Original Copyright from lpc21isp.c 
//                   (c) Martin Maurer 2003-2010, All rights reserved
//                   (c) by Aeolus Development 2004 http://www.aeolusdevelopment.com
//

#include "serialio.h"

#if defined COMPILE_FOR_WINDOWS || defined COMPILE_FOR_CYGWIN
static char RxTmpBuf[256];        // save received data to this buffer for half-duplex
char * pRxTmpBuf = RxTmpBuf;
#endif

void OControlModemLines(const termioType *pio, unsigned char DTR, unsigned char RTS);
unsigned char Ascii2Hex(unsigned char c);

#ifdef COMPILE_FOR_WINDOWS
void OSerialTimeoutSet(const termioType *pio, unsigned timeout_milliseconds);
int OSerialTimeoutCheck(const termioType *pio);
#endif // COMPILE_FOR_WINDOWS

/*static int AddFileHex(ISP_ENVIRONMENT *IspEnvironment, const char *arg);
static int AddFileBinary(ISP_ENVIRONMENT *IspEnvironment, const char *arg);
static int LoadFile(ISP_ENVIRONMENT *IspEnvironment, const char *filename, int FileFormat);*/

#define ERR_RECORD_TYPE_LOADFILE	55 /** File record type not yet implemented. */
#define ERR_ALLOC_FILE_LIST 60
#define ERR_FILE_OPEN_HEX	61	/**< Couldn't open hex file. */
#define ERR_FILE_SIZE_HEX	62	/**< Unexpected hex file size. */
#define ERR_FILE_ALLOC_HEX	63	/**< Couldn't allocate enough memory for hex file. */
#define ERR_FILE_ALLOC_BIN	64	/**< Couldn't allocate enough memory for bin file. */
#define ERR_FILE_RECST_HEX	65	/**< Can't find start of record indicator for Intel Hex file.*/
#define ERR_FILE_OPEN_BIN	66	/**< Couldn't open binary file. */
#define ERR_FILE_SIZE_BIN	67	/**< Unexpected binary file size. */
#define ERR_FILE_WRITE_BIN	68	/**< Couldn't write debug binary file to disk. How's that for ironic? */
#define ERR_MEMORY_RANGE    69  /**< Out of memory range. */

/************* Portability layer. Serial and console I/O differences    */
/* are taken care of here.                                              */

#if defined COMPILE_FOR_WINDOWS || defined COMPILE_FOR_CYGWIN
unsigned char OOpenSerialPort(termioType *pio, const char *port, const char *baud)
{
    DCB    dcb;
    COMMTIMEOUTS commtimeouts;
    
    memset(pio, sizeof(termioType), 0);

    pio->hCom = CreateFile(port, GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

    if (pio->hCom == INVALID_HANDLE_VALUE)
    {
        DebugPrintf(1, "Can't open COM-Port %s ! - Error: %ld\n", port, GetLastError());
		return 0;
    }

    DebugPrintf(3, "COM-Port %s opened...\n", port);

    GetCommState(pio->hCom, &dcb);
    dcb.BaudRate    = atol(baud);
    dcb.ByteSize    = 8;
    dcb.StopBits    = ONESTOPBIT;
    dcb.Parity      = NOPARITY;
    dcb.fDtrControl = DTR_CONTROL_DISABLE;
    dcb.fOutX       = FALSE;
    dcb.fInX        = FALSE;
    dcb.fNull       = FALSE;
    dcb.fRtsControl = RTS_CONTROL_DISABLE;

    // added by Herbert Demmel - iF CTS line has the wrong state, we would never send anything!
    dcb.fOutxCtsFlow = FALSE;
    dcb.fOutxDsrFlow = FALSE;

    if (SetCommState(pio->hCom, &dcb) == 0)
    {
        DebugPrintf(1, "Can't set baudrate %s ! - Error: %ld\n", baud, GetLastError());
        OCloseSerialPort(pio);
        return 0;
    }

   /*
    *  Peter Hayward 02 July 2008
    *
    *  The following call is only needed if the WaitCommEvent
    *  or possibly the GetCommMask functions are used.  They are
    *  *not* in this implimentation.  However, under Windows XP SP2
    *  on my laptop the use of this call causes XP to freeze (crash) while
    *  this program is running, e.g. in section 5/6/7 ... of a largish
    *  download.  Removing this *unnecessary* call fixed the problem.
    *  At the same time I've added a call to SetupComm to request
    *  (not necessarity honoured) the operating system to provide
    *  large I/O buffers for high speed I/O without handshaking.
    *
    *   SetCommMask(IspEnvironment->hCom,EV_RXCHAR | EV_TXEMPTY);
    */
    //SetupComm(IspEnvironment->hCom, 256, 256);

    SetCommMask(pio->hCom, EV_RXCHAR | EV_TXEMPTY);

    commtimeouts.ReadIntervalTimeout         = MAXDWORD;
    commtimeouts.ReadTotalTimeoutMultiplier  =    0;
    commtimeouts.ReadTotalTimeoutConstant    =    1;
    commtimeouts.WriteTotalTimeoutMultiplier =    0;
    commtimeouts.WriteTotalTimeoutConstant   =    0;
    SetCommTimeouts(pio->hCom, &commtimeouts);
	return 1;
}
#endif // defined COMPILE_FOR_WINDOWS || defined COMPILE_FOR_CYGWIN

#if defined COMPILE_FOR_LINUX
unsigned char OOpenSerialPort(termioType *pio,  const char *port, const char *baud)
{
    memset(pio, sizeof(termioType), 0);
    
    //DebugPrintf(1, "Opening port %s at %s\n", port, baud);
    
    pio->fdCom = open(port, O_RDWR | O_NOCTTY | O_NONBLOCK);

    if (pio->fdCom < 0)
    {
        int err = errno;
        DebugPrintf(1, "Can't open COM-Port %s ! (Error: %dd (0x%X))\n", port, err, err);
        return 0;
    }

    DebugPrintf(3, "COM-Port %s opened...\n", port);

    /* clear input & output buffers, then switch to "blocking mode" */
    tcflush(pio->fdCom, TCOFLUSH);
    tcflush(pio->fdCom, TCIFLUSH);
    fcntl(pio->fdCom, F_SETFL, fcntl(pio->fdCom, F_GETFL) & ~O_NONBLOCK);

    tcgetattr(pio->fdCom, &pio->oldtio); /* save current port settings */

    bzero(&pio->newtio, sizeof(pio->newtio));
    pio->newtio.c_cflag = CS8 | CLOCAL | CREAD;

#if defined(__FreeBSD__) || defined(__OpenBSD__)

    if(cfsetspeed(&pio->newtio,(speed_t) strtol(baud,NULL,10))) {
                  DebugPrintf(1, "baudrate %s not supported\n", baud);
                  CloseSerialPort(pio);
                  return 0;
              };
#else

#ifdef __APPLE__
#define NEWTERMIOS_SETBAUDARTE(bps) pio->newtio.c_ispeed = pio->newtio.c_ospeed = B19200;

#ifndef B1152000
#define B1152000 1152000
#endif

#ifndef B576000
#define B576000	576000
#endif
	
#else
#define NEWTERMIOS_SETBAUDARTE(bps) pio->newtio.c_cflag |= bps;
#endif



    switch (atol(baud))
    {
#ifdef B1152000
          case 1152000: NEWTERMIOS_SETBAUDARTE(B1152000); break;
#endif // B1152000
#ifdef B576000
          case  576000: NEWTERMIOS_SETBAUDARTE(B576000); break;;
#endif // B576000
#ifdef B230400
          case  230400: NEWTERMIOS_SETBAUDARTE(B230400); break;
#endif // B230400
#ifdef B115200
          case  115200: NEWTERMIOS_SETBAUDARTE(B115200); break;
#endif // B115200
#ifdef B57600
          case   57600: NEWTERMIOS_SETBAUDARTE(B57600); break;
#endif // B57600
#ifdef B38400
          case   38400: NEWTERMIOS_SETBAUDARTE(B38400); break;
#endif // B38400
#ifdef B19200
          case   19200: NEWTERMIOS_SETBAUDARTE(B19200); break;
#endif // B19200
#ifdef B9600
          case    9600: NEWTERMIOS_SETBAUDARTE(B9600); break;
#endif // B9600
          default:
              {
                  DebugPrintf(1, "unknown baudrate %s, provided: %d\n", baud, atol(baud));
                  OCloseSerialPort(pio);
                  return 0;
              }
    }

#endif

    pio->newtio.c_iflag = IGNPAR | IGNBRK | IXON | IXOFF;
    pio->newtio.c_oflag = 0;

    /* set input mode (non-canonical, no echo,...) */
    pio->newtio.c_lflag = 0;

    cfmakeraw(&pio->newtio);
    pio->newtio.c_cc[VTIME]    = 1;   /* inter-character timer used */
    pio->newtio.c_cc[VMIN]     = 0;   /* blocking read until 0 chars received */

    tcflush(pio->fdCom, TCIFLUSH);
    if(tcsetattr(pio->fdCom, TCSANOW, &pio->newtio))
    {
       DebugPrintf(1, "Could not change serial port behaviour (wrong baudrate?)\n");
       return 0;
    }
	
#ifdef __APPLE__
#ifndef IOSSIOSPEED
#define IOSSIOSPEED _IOW('T', 2, speed_t)
#endif
	speed_t speed= atol(baud);
	ioctl(pio->fdCom, IOSSIOSPEED, &speed);
#endif
	return 1;
}
#endif // defined COMPILE_FOR_LINUX

#if defined COMPILE_FOR_WINDOWS || defined COMPILE_FOR_CYGWIN
void OCloseSerialPort(termioType *pio)
{
    CloseHandle(pio->hCom);
}

#endif // defined COMPILE_FOR_WINDOWS || defined COMPILE_FOR_CYGWIN

#if defined COMPILE_FOR_LINUX
void OCloseSerialPort(termioType *pio)
{
    tcflush(pio->fdCom, TCOFLUSH);
    tcflush(pio->fdCom, TCIFLUSH);
    tcsetattr(pio->fdCom, TCSANOW, &pio->oldtio);

    close(pio->fdCom);
}
#endif // defined COMPILE_FOR_LINUX


/***************************** SendComPortBlock *************************/
/**  Sends a block of bytes out the opened com port.
\param [in] s block to send.
\param [in] n size of the block.
*/
void OSendComPortBlock(const termioType *pio, const void *s, size_t n)
{
#if defined COMPILE_FOR_WINDOWS || defined COMPILE_FOR_CYGWIN

    unsigned int realsize;
    size_t m;
    unsigned int rxsize;
    char * pch;
    char * rxpch;
#endif // defined COMPILE_FOR_WINDOWS || defined COMPILE_FOR_CYGWIN

    DumpString(4, s, n, "Sending ");

#if defined COMPILE_FOR_WINDOWS || defined COMPILE_FOR_CYGWIN

    if (pio->HalfDuplex == 0)
    {
        WriteFile(pio->hCom, s, n, &realsize, NULL);
    }
    else
    {
        pch = (char *)s;
        rxpch = RxTmpBuf;
        pRxTmpBuf = RxTmpBuf;

        // avoid buffer otherflow
        if (n > sizeof (RxTmpBuf))
            n = sizeof (RxTmpBuf);

        for (m = 0; m < n; m++)
        {
            WriteFile(pio->hCom, pch, 1, &realsize, NULL);

            if ((*pch != '?') || (n != 1))
            {
                do
                {
                    ReadFile(pio->hCom, rxpch, 1, &rxsize, NULL);
                }while (rxsize == 0);
            }
            pch++;
            rxpch++;
        }
        *rxpch = 0;        // terminate echo string
    }
#endif // defined COMPILE_FOR_WINDOWS || defined COMPILE_FOR_CYGWIN

#if defined COMPILE_FOR_LINUX || defined COMPILE_FOR_LPC21

    write(pio->fdCom, s, n);
	
#ifndef __APPLE__
    //Must not flush the buffers on OS X as this causes bytes to be lost
	tcflush(pio->fdCom, TCOFLUSH);
	fflush( NULL );
#endif

#endif // defined COMPILE_FOR_LINUX || defined COMPILE_FOR_LPC21
}

/***************************** SendComPort ******************************/
/**  Sends a string out the opened com port.
\param [in] s string to send.
*/
void OSendComPort(const termioType *pio, const char *s)
{
    OSendComPortBlock(pio, s, strlen(s));
}

/***************************** SerialTimeoutTick ************************/
/**  Performs a timer tick.  In this simple case all we do is count down
with protection against underflow and wrapping at the low end.
*/
void OSerialTimeoutTick(const termioType *pio)
{
	//Intentional cast from cost to non-const
	termioType *lpio = (termioType *)pio;  
	
    if (lpio->serial_timeout_count <= 1)
    {
        lpio->serial_timeout_count = 0;
    }
    else
    {
	    Sleep(1);
        lpio->serial_timeout_count--;
    }
}


/***************************** ReceiveComPortBlock **********************/
/**  Receives a buffer from the open com port. Returns all the characters
ready (waits for up to 'n' milliseconds before accepting that no more
characters are ready) or when the buffer is full. 'n' is system dependant,
see SerialTimeout routines.
\param [out] answer buffer to hold the bytes read from the serial port.
\param [in] max_size the size of buffer pointed to by answer.
\param [out] real_size pointer to a int that returns the amout of the
buffer that is actually used.
*/
void OReceiveComPortBlock(const termioType *pio,
                                          void *answer, unsigned int max_size,
                                          unsigned int *real_size)
{
    char tmp_string[32];

#if defined COMPILE_FOR_WINDOWS || defined COMPILE_FOR_CYGWIN

    if (pio->HalfDuplex == 0)
        ReadFile(pio->hCom, answer, max_size, real_size, NULL);
    else
    {
        *real_size = strlen (pRxTmpBuf);
        if (*real_size)
        {
            if (max_size >= *real_size)
            {
                strncpy((char*) answer, pRxTmpBuf, *real_size);
                RxTmpBuf[0] = 0;
                pRxTmpBuf = RxTmpBuf;
            }
            else
            {
                strncpy((char*) answer, pRxTmpBuf, max_size);
                *real_size = max_size;
                pRxTmpBuf += max_size;
            }
        }
        else
            ReadFile(pio->hCom, answer, max_size, real_size, NULL);
    }

#endif // defined COMPILE_FOR_WINDOWS || defined COMPILE_FOR_CYGWIN

#if defined COMPILE_FOR_LINUX || defined COMPILE_FOR_LPC21

    *real_size = read(pio->fdCom, answer, max_size);

#endif // defined COMPILE_FOR_LINUX

    sprintf(tmp_string, "Read(Length=%d): ", (*real_size));
    DumpString(3, answer, (*real_size), tmp_string);

    if (*real_size == 0)
    {
        OSerialTimeoutTick(pio);
    }
}


/***************************** SerialTimeoutSet *************************/
/**  Sets (or resets) the timeout to the timout period requested.  Starts
counting to this period.  This timeout support is a little odd in that the
timeout specifies the accumulated deadtime waiting to read not the total
time waiting to read. They should be close enought to the same for this
use. Used by the serial input routines, the actual counting takes place in
ReceiveComPortBlock.
\param [in] timeout_milliseconds the time in milliseconds to use for
timeout.  Note that just because it is set in milliseconds doesn't mean
that the granularity is that fine.  In many cases (particularly Linux) it
will be coarser.
*/
void OSerialTimeoutSet(const termioType *pio, unsigned timeout_milliseconds)
{
	termioType *lpio = (termioType *)pio;
#if defined __APPLE__
	lpio->serial_timeout_count = timeout_milliseconds / 100;
#elif defined COMPILE_FOR_LINUX
    lpio->serial_timeout_count = timeout_milliseconds / 100;
#elif defined COMPILE_FOR_LPC21
    lpio->serial_timeout_count = timeout_milliseconds * 200;
#else
    lpio->serial_timeout_count = timeout_milliseconds;
#endif
}



/***************************** SerialTimeoutCheck ***********************/
/**  Check to see if the serial timeout timer has run down.
\retval 1 if timer has run out.
\retval 0 if timer still has time left.
*/
int OSerialTimeoutCheck(const termioType *pio)
{
    if (pio->serial_timeout_count == 0)
    {
        return 1;
    }
    return 0;
}

#if !defined COMPILE_FOR_LPC21
/***************************** ControlModemLines ************************/
/**  Controls the modem lines to place the microcontroller into various
states during the programming process.
error rather abruptly terminates the program.
\param [in] DTR the state to set the DTR line to.
\param [in] RTS the state to set the RTS line to.
*/
void OControlModemLines(const termioType *pio, unsigned char DTR, unsigned char RTS)
{
    //handle wether to invert the control lines:
    DTR ^= pio->ControlLinesInverted;
    RTS ^= pio->ControlLinesInverted;

    //handle wether to swap the control lines
    if (pio->ControlLinesSwapped)
    {
        unsigned char tempRTS;
        tempRTS = RTS;
        RTS = DTR;
        DTR = tempRTS;
    }

#if defined COMPILE_FOR_LINUX
    int status;

    if (ioctl(pio->fdCom, TIOCMGET, &status) == 0)
    {
        DebugPrintf(3, "ioctl get ok, status = %X\n",status);
    }
    else
    {
        DebugPrintf(1, "ioctl get failed\n");
    }

    if (DTR) status |=  TIOCM_DTR;
    else    status &= ~TIOCM_DTR;

    if (RTS) status |=  TIOCM_RTS;
    else    status &= ~TIOCM_RTS;

    if (ioctl(pio->fdCom, TIOCMSET, &status) == 0)
    {
        DebugPrintf(3, "ioctl set ok, status = %X\n",status);
    }
    else
    {
        DebugPrintf(1, "ioctl set failed\n");
    }

    if (ioctl(pio->fdCom, TIOCMGET, &status) == 0)
    {
        DebugPrintf(3, "ioctl get ok, status = %X\n",status);
    }
    else
    {
        DebugPrintf(1, "ioctl get failed\n");
    }

#endif // defined COMPILE_FOR_LINUX
#if defined COMPILE_FOR_WINDOWS || defined COMPILE_FOR_CYGWIN

    if (DTR) EscapeCommFunction(pio->hCom, SETDTR);
    else    EscapeCommFunction(pio->hCom, CLRDTR);

    if (RTS) EscapeCommFunction(pio->hCom, SETRTS);
    else    EscapeCommFunction(pio->hCom, CLRRTS);

#endif // defined COMPILE_FOR_WINDOWS || defined COMPILE_FOR_CYGWIN

#if defined COMPILE_FOR_LPC21
    LPC_RESET(DTR);
    LPC_BSL(RTS);
#endif

    DebugPrintf(3, "DTR (%d), RTS (%d)\n", DTR, RTS);
}


/***************************** ClearSerialPortBuffers********************/
/**  Empty the serial port buffers.  Cleans things to a known state.
*/
void OClearSerialPortBuffers(const termioType *pio)
{
#if defined COMPILE_FOR_LINUX
    /* variables to store the current tty state, create a new one */
    struct termios origtty, tty;

    /* store the current tty settings */
    tcgetattr(pio->fdCom, &origtty);

    // Flush input and output buffers
    tty=origtty;
    tcsetattr(pio->fdCom, TCSAFLUSH, &tty);

    /* reset the tty to its original settings */
    tcsetattr(pio->fdCom, TCSADRAIN, &origtty);
	
	//tcflush(pio->fdCom, TCOFLUSH);
	//tcflush(pio->fdCom, TCIFLUSH);
	//fflush( NULL );	
	
#endif // defined COMPILE_FOR_LINUX
#if defined COMPILE_FOR_WINDOWS || defined COMPILE_FOR_CYGWIN
    PurgeComm(pio->hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR);
#endif // defined COMPILE_FOR_WINDOWS || defined COMPILE_FOR_CYGWIN
}
#endif // !defined COMPILE_FOR_LPC21

/************* Applicationlayer.                                        */

/***************************** ReceiveComPort ***************************/
/**  Receives a buffer from the open com port. Returns when the buffer is
filled, the numer of requested linefeeds has been received or the timeout
period has passed
\param [in] ISPEnvironment.
\param [out] Answer buffer to hold the bytes read from the serial port.
\param [in] MaxSize the size of buffer pointed to by Answer.
\param [out] RealSize pointer to a int that returns the amout of the
buffer that is actually used.
\param [in] WantedNr0x0A the maximum number of linefeeds to accept before
returning.
\param [in] timeOutMilliseconds the maximum amount of time to wait before
reading with an incomplete buffer.
*/
void OReceiveComPort(const termioType *pio,
                    const char *Ans, unsigned int MaxSize,
                    unsigned int *RealSize, unsigned int WantedNr0x0A,
                    unsigned timeOutMilliseconds)
{
    unsigned int tmp_realsize;
    unsigned int nr_of_0x0A = 0;
    unsigned int nr_of_0x0D = 0;
    int eof = 0;
    unsigned int p;
    unsigned char *Answer;

    Answer = (unsigned char*) Ans;

    OSerialTimeoutSet(pio, timeOutMilliseconds);

    (*RealSize) = 0;

    for(;((*RealSize) < MaxSize);)
    {
        OReceiveComPortBlock(pio, Answer + (*RealSize), MaxSize - 1 - (*RealSize), &tmp_realsize);

        if (tmp_realsize != 0)
        {
            for (p = (*RealSize); p < (*RealSize) + tmp_realsize; p++)
            {
                if (Answer[p] == 0x0a)
                {
                    nr_of_0x0A++;
                }
                else if (Answer[p] == 0x0d)
                {
                    nr_of_0x0D++;
                }
                /*else if (((signed char) Answer[p]) < 0)
                {
                    eof = 1;
                }*/
            }
        }

        (*RealSize) += tmp_realsize;
		
		if (OSerialTimeoutCheck(pio)) 
		{
		    DebugPrintf(4, "OReceiveComPort timeout exit");
			break;
		}
		if (nr_of_0x0A >= WantedNr0x0A) 
		{
		    DebugPrintf(4, "OReceiveComPort 0xa exit");
		    break;
		}
		if (nr_of_0x0D >= WantedNr0x0A) 
		{
		    DebugPrintf(4, "OReceiveComPort 0xd exit");
			break;
		}
		if (eof) 
		{
		    DebugPrintf(4, "OReceiveComPort eof exit");
			break;
		}
    }
	
    Answer[(*RealSize)] = 0;
	
	DebugPrintf(4, " with: '%s'\n", Answer); 

    //sprintf(tmp_string, "OReceiveComPort Answer(Length=%ld): ", (*RealSize));
    //DumpString(3, Answer, (*RealSize), tmp_string);
}


//#if !defined COMPILE_FOR_LPC21

/***************************** ReceiveComPortBlockComplete **************/
/**  Receives a fixed block from the open com port. Returns when the
block is completely filled or the timeout period has passed
\param [out] block buffer to hold the bytes read from the serial port.
\param [in] size the size of the buffer pointed to by block.
\param [in] timeOut the maximum amount of time to wait before guvung up on
completing the read.
\return 0 if successful, non-zero otherwise.
*/
int OReceiveComPortBlockComplete(const termioType *pio, void *block, size_t size, unsigned timeout)
{
    unsigned int realsize = 0, read;
    char *result;

    result = (char*) block;

    OSerialTimeoutSet(pio, timeout);

    do
    {
        OReceiveComPortBlock(pio, result + realsize, size - realsize, &read);

        realsize += read;

    } while ((realsize < size) && (OSerialTimeoutCheck(pio) == 0));

    //sprintf(tmp_string, "Answer(Length=%ld): ", realsize);
    //DumpString(3, result, realsize, tmp_string);

    if (realsize != size)
    {
        return 1;
    }
    return 0;
}

